// 压缩图片
function compressImg (imgSrc, imgWidth = 8) {
  return new Promise((resolve, reject) => {
    if (!imgSrc) { reject('imgSrc can not be empty!') }
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')
    const img = new Image()
    img.crossOrigin = 'Anonymous'
    img.onload = function () {
      canvas.width = imgWidth
      canvas.height = imgWidth
      ctx?.drawImage(img, 0, 0, imgWidth, imgWidth)
      const data = ctx?.getImageData(0, 0, imgWidth, imgWidth)
      resolve(data)
    }
    img.src = imgSrc
  })
}
function compressImg1 (imgObj, imgWidth = 8) {
  return new Promise((resolve, reject) => {
    if (!imgObj) { reject('imgObj can not be empty!') }
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')
    canvas.width = imgWidth
    canvas.height = imgWidth
    ctx?.drawImage(imgObj, 0, 0, imgWidth, imgWidth)
    const data = ctx?.getImageData(0, 0, imgWidth, imgWidth)
    resolve(data)
  })
}
function compressImg2 (imgObj, imgWidth = 8) {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  canvas.width = imgWidth
  canvas.height = imgWidth
  ctx?.drawImage(imgObj, 0, 0, imgWidth, imgWidth)
  return ctx?.getImageData(0, 0, imgWidth, imgWidth)
}
// 根据 ImageData 得到 base64 图片地址
function imgData2imgUrl (imgData, imgWidth) {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  canvas.width = imgWidth
  canvas.height = imgWidth
  ctx?.putImageData(imgData, 0, 0)
  return canvas.toDataURL('image/png')
}
// 根据 RGBA 数组生成 ImageData
function createImgData (dataDetail) {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  const imgWidth = Math.sqrt(dataDetail.length / 4)
  const newImageData = ctx?.createImageData(imgWidth, imgWidth)
  for (let i = 0; i < dataDetail.length; i += 4) {
    let R = dataDetail[i]
    let G = dataDetail[i + 1]
    let B = dataDetail[i + 2]
    let Alpha = dataDetail[i + 3]
    newImageData.data[i] = R
    newImageData.data[i + 1] = G
    newImageData.data[i + 2] = B
    newImageData.data[i + 3] = Alpha
  }
  return newImageData
}
// 彩色图像数据转灰度图像数据
function createGrayscale (imgData) {
  const newData = Array(imgData.data.length)
  newData.fill(0)
  imgData.data.forEach((_data, index) => {
    if ((index + 1) % 4 === 0) {
      const R = imgData.data[index - 3]
      const G = imgData.data[index - 2]
      const B = imgData.data[index - 1]
      const gray = ~~((R + G + B) / 3)
      newData[index - 3] = gray
      newData[index - 2] = gray
      newData[index - 1] = gray
      newData[index] = 255 // Alpha 值固定为255
    }
  })
  return createImgData(newData)
}
// [算法1]平均哈希算法
function getHashFingerprint (imgData) {
  const grayList = imgData.data.reduce((pre, cur, index) => {
    if ((index + 1) % 4 === 0) {
      pre.push(imgData.data[index - 1])
    }
    return pre
  }, [])
  const length = grayList.length
  const grayAverage = grayList.reduce((pre, next) => (pre + next), 0) / length
  return grayList.map(gray => (gray >= grayAverage ? 1 : 0)).join('')
}
// [算法2]感知哈希算法
function memoizeCosines (N, cosMap) {
  cosMap = cosMap || {}
  cosMap[N] = new Array(N * N)
  let PI_N = Math.PI / N
  for (let k = 0; k < N; k++) {
    for (let n = 0; n < N; n++) {
      cosMap[N][n + (k * N)] = Math.cos(PI_N * (n + 0.5) * k)
    }
  }
  return cosMap
}
function dct (signal, scale = 2) {
  let L = signal.length
  let cosMap = null
  if (!cosMap || !cosMap[L]) {
    cosMap = memoizeCosines(L, cosMap)
  }
  let coefficients = signal.map(function () { return 0 })
  return coefficients.map(function (_, ix) {
    return scale * signal.reduce(function (prev, cur, index) {
      return prev + (cur * cosMap[L][index + (ix * L)])
    }, 0)
  })
}
function createMatrix (arr) { // 一维数组升维
  const length = arr.length
  const matrixWidth = Math.sqrt(length)
  const matrix = []
  for (let i = 0; i < matrixWidth; i++) {
    const _temp = arr.slice(i * matrixWidth, i * matrixWidth + matrixWidth)
    matrix.push(_temp)
  }
  return matrix
}
function getMatrixRange (matrix, range = 1) { // 从矩阵中获取其“左上角”大小为 range × range 的内容
  const rangeMatrix = []
  for (let i = 0; i < range; i++) {
    for (let j = 0; j < range; j++) {
      rangeMatrix.push(matrix[i][j])
    }
  }
  return rangeMatrix
}
function getPHashFingerprint (imgData) {
  const dctData = dct(imgData.data)
  const dctMatrix = createMatrix(dctData)
  const rangeMatrix = getMatrixRange(dctMatrix, dctMatrix.length / 8)
  const rangeAve = rangeMatrix.reduce((pre, cur) => pre + cur, 0) / rangeMatrix.length
  return rangeMatrix.map(val => (val >= rangeAve ? 1 : 0)).join('')
}
// [算法3]颜色分布法
function simplifyColorData (imgData, zoneAmount = 4) { // 划分颜色区间，默认区间数目为4个(把256种颜色取值简化为4种)
  const colorZoneDataList = []
  const zoneStep = 256 / zoneAmount
  const zoneBorder = [0] // 区间边界
  for (let i = 1; i <= zoneAmount; i++) {
    zoneBorder.push(zoneStep * i - 1)
  }
  imgData.data.forEach((data, index) => {
    if ((index + 1) % 4 !== 0) {
      for (let i = 0; i < zoneBorder.length; i++) {
        if (data > zoneBorder[i] && data <= zoneBorder[i + 1]) {
          data = i
        }
      }
    }
    colorZoneDataList.push(data)
  })
  return colorZoneDataList
}
function seperateListToColorZone (simplifiedDataList) {
  const zonedList = []
  let tempZone = []
  simplifiedDataList.forEach((data, index) => {
    if ((index + 1) % 4 !== 0) {
      tempZone.push(data)
    } else {
      zonedList.push(JSON.stringify(tempZone))
      tempZone = []
    }
  })
  return zonedList
}
function getColorFingerprint (zonedList, zoneAmount = 16) {
  const colorSeperateMap = {}
  for (let i = 0; i < zoneAmount; i++) {
    for (let j = 0; j < zoneAmount; j++) {
      for (let k = 0; k < zoneAmount; k++) {
        colorSeperateMap[JSON.stringify([i, j, k])] = 0
      }
    }
  }
  zonedList.forEach(zone => {
    colorSeperateMap[zone]++
  })
  return Object.values(colorSeperateMap)
}
// [算法4]内容特征法
let GrayscaleWeight = { R: 0.299, G: 0.587, B: 0.114 }
function toGray (imgData) { // 灰度处理
  const grayData = []
  const data = imgData.data
  for (let i = 0; i < data.length; i += 4) {
    const gray = ~~(data[i] * GrayscaleWeight.R + data[i + 1] * GrayscaleWeight.G + data[i + 2] * GrayscaleWeight.B)
    data[i] = data[i + 1] = data[i + 2] = gray
    grayData.push(gray)
  }
  return grayData
}
// rewrite from http://www.labbookpages.co.uk/software/imgProc/otsuThreshold.html
function OTSUAlgorithm (imgData) {
  const grayData = toGray(imgData)
  let ptr = 0
  let histData = Array(256).fill(0)
  let total = grayData.length
  while (ptr < total) {
    let h = 0xFF & grayData[ptr++]
    histData[h]++
  }
  let sum = 0
  for (let i = 0; i < 256; i++) {
    sum += i * histData[i]
  }
  let wB = 0
  let wF = 0
  let sumB = 0
  let varMax = 0
  let threshold = 0
  for (let t = 0; t < 256; t++) {
    wB += histData[t]
    if (wB === 0) continue
    wF = total - wB
    if (wF === 0) break
    sumB += t * histData[t]
    let mB = sumB / wB
    let mF = (sum - sumB) / wF
    let varBetween = wB * wF * (mB - mF) ** 2
    if (varBetween > varMax) {
      varMax = varBetween
      threshold = t
    }
  }
  return threshold
}
function binaryzation (imgData, threshold) {
  const canvas = document.createElement('canvas')
  const ctx = canvas.getContext('2d')
  const imgWidth = Math.sqrt(imgData.data.length / 4)
  const newImageData = ctx?.createImageData(imgWidth, imgWidth)
  for (let i = 0; i < imgData.data.length; i += 4) {
    let R = imgData.data[i]
    let G = imgData.data[i + 1]
    let B = imgData.data[i + 2]
    let Alpha = imgData.data[i + 3]
    let sum = (R + G + B) / 3
    newImageData.data[i] = sum > threshold ? 255 : 0
    newImageData.data[i + 1] = sum > threshold ? 255 : 0
    newImageData.data[i + 2] = sum > threshold ? 255 : 0
    newImageData.data[i + 3] = Alpha
  }
  return newImageData
}
function getOTSUFingerprint (imgData, threshold) {
  let result = []
  for (let i = 0; i < imgData.data.length; i += 4) {
    let R = imgData.data[i]
    let G = imgData.data[i + 1]
    let B = imgData.data[i + 2]
    let Alpha = imgData.data[i + 3]
    let sum = (R + G + B) / 3
    result.push(Alpha > 0 ? (sum > threshold ? 1 : 0) : 0)
  }
  return result.join('')
}
// [比对算法1]汉明距离
function hammingDistance (str1, str2) {
  let distance = 0
  const str1Arr = str1.split('')
  const str2Arr = str2.split('')
  str1Arr.forEach((letter, index) => {
    if (letter !== str2Arr[index]) {
      distance++
    }
  })
  return distance
}
function hammingSimilarity (str1, str2) {
  let distance = hammingDistance(str1, str2)
  let totalLen = str1.length
  let result = parseFloat((1 - distance / totalLen).toFixed(4))
  return Math.min(result, 1)
}
// [比对算法2]余弦相似度
function cosineSimilarity (sampleFingerprint, targetFingerprint) {
  // cosθ = ∑n, i=1(Ai × Bi) / (√∑n, i=1(Ai)^2) × (√∑n, i=1(Bi)^2) = A · B / |A| × |B|
  const length = sampleFingerprint.length
  let innerProduct = 0
  for (let i = 0; i < length; i++) {
    innerProduct += sampleFingerprint[i] * targetFingerprint[i]
  }
  let vecA = 0
  let vecB = 0
  for (let i = 0; i < length; i++) {
    vecA += sampleFingerprint[i] ** 2
    vecB += targetFingerprint[i] ** 2
  }
  const outerProduct = Math.sqrt(vecA) * Math.sqrt(vecB)
  let result = parseFloat((innerProduct / outerProduct).toFixed(4))
  return Math.min(result, 1)
}

module.exports = {
  compressImg,
  compressImg1,
  compressImg2,
  imgData2imgUrl,
  createImgData,
  createGrayscale,
  getHashFingerprint,
  getPHashFingerprint,
  simplifyColorData,
  seperateListToColorZone,
  getColorFingerprint,
  OTSUAlgorithm,
  binaryzation,
  getOTSUFingerprint,
  hammingDistance,
  hammingSimilarity,
  cosineSimilarity
}
